#include "libcomm.h"
#if defined(__GNUC__) && !defined(__CYGWIN__)
#include <sys/syscall.h>
#endif

#ifdef __GNUC__
static void __wait_cleanup(void *arg)
{    
	LOGWARN("thread(%d) was canceled while Thread::Sleep().",Thread::Id());
}
#endif

void Thread::Sleep(unsigned int timeoutMs)
{
#ifdef COMM_MSVC
	::Sleep(timeoutMs);
#else
	pthread_cleanup_push(__wait_cleanup, NULL);
	MYLIB_ASSERT_ERRNO(0 == usleep(1000*timeoutMs));
	pthread_cleanup_pop(0);
#endif
}

int Thread::Id()
{
#ifdef COMM_MSVC
	DWORD dwId = ::GetCurrentThreadId();
	MYLIB_ASSERT(dwId <= 0x7FFFFFFF);
	return dwId;
#elif defined(__CYGWIN__)
	#warning "!!!was a pseudo thread id in cygwin version!!!"
	unsigned long long pid = (unsigned long long)pthread_self();
	return (Bitset::High(pid) ^ Bitset::Low(pid));
#else
	return syscall(SYS_gettid);
#endif
}

class DefaultThread : public Thread
{
public:
	Thread::ThreadProc m_proc;
	CommType m_arg;
	DefaultThread(Thread::ThreadProc proc, const CommType& arg, const char* threadName)
		:Thread(threadName),m_proc(proc),m_arg(arg){}
	virtual int Process()
	{
		return m_proc(m_arg);
	}
};

Thread* Thread::Run(Thread::ThreadProc proc, const CommType& arg, const char* threadName)
{
	MYLIB_ASSERT_NOTNULL(proc);
	DefaultThread* trd = new DefaultThread(proc,arg,threadName);
	MYLIB_ASSERT_NOTNULL(trd);
	trd->Run();
	return static_cast<Thread*>(trd);
}

////////////////////////////////////////////////////////////////////////// 


Thread::Thread(const char* threadName)
	: m_name(threadName),m_state(TS_INIT),m_exitcode(EXIT_CANCELED),m_thread(0)
{
	if(m_name.empty())
	{
		static int index = 0;
		m_name = String::format("trd_%03d",++index);
	}
}

Thread::~Thread(void)
{
}

Thread::handle Thread::Handle() const
{
	return m_thread;
}

const char* Thread::Name() const
{
	return (const char*)m_name;
}

#ifdef COMM_MSVC
DWORD WINAPI Thread::__ThreadProc(void* arg)
{
	int tid = Thread::Id();
	Thread* self = (Thread*)arg;
	LOGNOTICE("thread(%d) %s is running...", tid, self->Name());
	self->m_exitcode = self->Process();
	LOGNOTICE("thread(%d) %s exit, exit code: %d.", tid, self->Name(),self->m_exitcode);
	MYLIB_ASSERT(self->m_exitcode<EXIT_MIN||self->m_exitcode>EXIT_MAX);
	self->m_state = TS_EXIT;
	return self->m_exitcode;
}
#else
void* Thread::__ThreadProc(void* arg)
{
	MYLIB_ASSERT_VALUE(0,pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
	MYLIB_ASSERT_VALUE(0,pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL));

	int tid = Thread::Id();
	Thread* self = (Thread*)arg;
	LOGNOTICE("thread(%d) %s is running...", tid, self->Name());
	self->m_exitcode = self->Process();
	LOGNOTICE("thread(%d) %s exit, exit code: %d.", tid, self->Name(),self->m_exitcode);
	MYLIB_ASSERT(self->m_exitcode<EXIT_MIN||self->m_exitcode>EXIT_MAX);
	self->m_state = TS_EXIT;
	return (reinterpret_cast<void*>(self->m_exitcode));
}
#endif

void Thread::Run()
{
	if(m_state != TS_INIT)
		return;
	m_exitcode = EXIT_CANCELED;
#ifdef COMM_MSVC
	MYLIB_ASSERT_ERRNO(NULL != (m_thread = CreateThread(NULL,0,__ThreadProc,(LPVOID)this,0,0)));
#else
	MYLIB_ASSERT_VALUE(0,pthread_attr_init(&m_attr));
	MYLIB_ASSERT_VALUE(0,pthread_attr_setdetachstate(&m_attr, PTHREAD_CREATE_JOINABLE));
	MYLIB_ASSERT_VALUE(0,pthread_create(&m_thread, &m_attr, __ThreadProc, this));
#endif
	m_state = TS_RUNNING;
}

void Thread::Terminate()
{
	if(m_state != TS_RUNNING || 0 == m_thread)
		return;
#ifdef COMM_MSVC
	MYLIB_ASSERT_ERRNO(TerminateThread(m_thread,EXIT_CANCELED));
#else
	errno = pthread_cancel(m_thread);
	MYLIB_ASSERT_ERRNO(0 == errno || ESRCH == errno);
#endif
	LOGNOTICE("thread %s was terminated by manual.", this->Name());
	m_state = TS_EXIT;
}

int Thread::Join(unsigned int timeoutMs)
{
	if(m_state == TS_RUNNING)
	{
		MYLIB_ASSERT_NOTNULL(m_thread);
#ifdef COMM_MSVC
		
		DWORD dwEvent = WaitForSingleObject(m_thread,timeoutMs); 
		switch (dwEvent) 
		{ 
		case WAIT_OBJECT_0: 
			break;
		case WAIT_TIMEOUT: 
			{
				m_exitcode = EXIT_TIMEOUT;
				LOGWARN("thread %s join timeout after %d ms.", this->Name(), timeoutMs);
			}
			break;
		default: 
			{
				m_exitcode = EXIT_ERROR;
				LOGFUNCERROR(WaitForSingleObject);
			}
			break;
		}

#else

		int ret = 0;
		if (-1 == timeoutMs)
		{
			ret = pthread_join(m_thread,NULL);
		}
		else
		{
			timespec ts = Timer::MakeTimespec(timeoutMs);
			ret = pthread_timedjoin_np(m_thread,NULL,&ts);
		}

		if(0 != ret && ESRCH != ret) // ESRCH: thread have exit before.
		{
			if(ETIMEDOUT == ret)
			{
				m_exitcode = EXIT_TIMEOUT;
				LOGWARN("thread %s join timeout after %d ms.", this->Name(), timeoutMs);
			}
			else
			{
				m_exitcode = EXIT_ERROR;
				LOGFUNCERRORNO(pthread_timedjoin_np, ret);
			}
		}

#endif
	}

	// clear resource.
	//
	if(EXIT_TIMEOUT != m_exitcode)
	{
		if(0 != m_thread)
		{
#ifdef COMM_MSVC
			MYLIB_ASSERT_ERRNO(CloseHandle(m_thread));
#else
			MYLIB_ASSERT_VALUE(0,pthread_attr_destroy(&m_attr));
#endif
		}
		m_thread = 0;
		m_state = TS_INIT;
	}
	
	return m_exitcode;
}

